import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FilenameFilter;
import java.util.Enumeration;
import java.util.Vector;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JApplet;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 * The GUI for the EB-forms-annotation-editor, as created by <strong>AXXXE</strong>.
 * @author AXXXE
 *
 */
public class Gui extends JApplet implements ActionListener,
TreeSelectionListener {

	final static long serialVersionUID = 1;

	final static Dimension filler = new Dimension(5, 5);

	// the xml handler
	protected XMLHandler xmlHandler;

	// the current dictionary item name
	private String dictionaryItemName;

	// the configuration filename
	private String configFilename;

	private Vector<String> suppProperties = null;

	private Vector<JComboBox> suppBoxes = null;

	private JComboBox selectComponentBox;

	private DefaultMutableTreeNode root;

	// swing components
	private JTree tree;

	private JPanel settingsPanel;

	private JPanel buttonPanel;

	private JButton upButton;

	private JButton downButton;

	private JButton openButton;

	private JButton saveButton;

	private JLabel statusLabel;

	public void init() { // must be public, because of being an applet
		try {
			if (!openFileDialog()) {
				throw new Exception("You must select a config file");
			}

			xmlHandler = new XMLHandler(configFilename);
			root = xmlHandler.getSchemafileHandler().getTree();
			correctOrder(root);
			initializeGui();
			initializesettings();
		} catch (Exception e) {
			showError("Can not parse schema file:" + e.getMessage());
			e.printStackTrace();
			errorState();
		}
	}
	
	private void initializeGui() {
		this.setSize(600, 500);
		getContentPane().setVisible(false);
		getContentPane().removeAll();

		// main container
		JPanel mainVerticalPanel = new JPanel();
		mainVerticalPanel.setBackground(Color.white);
		mainVerticalPanel.setLayout(new BorderLayout());

		tree = new JTree(root);
		tree.getSelectionModel().setSelectionMode(
				TreeSelectionModel.SINGLE_TREE_SELECTION);
		tree.addTreeSelectionListener(this);
		JScrollPane treePane = new JScrollPane(tree);
		treePane.setPreferredSize(new Dimension(600, 200));

		// A panel for the components list and the up and down buttons
		JPanel selectComponentPanel = new JPanel();
		selectComponentPanel.setLayout(new BorderLayout());
		selectComponentPanel.setBackground(Color.white);
		Border listPanelBorder = BorderFactory.createTitledBorder("Select Component");
		selectComponentPanel.setBorder(listPanelBorder);

		JPanel treeAndButtons = new JPanel();
		treeAndButtons.setLayout(new BorderLayout());
		treeAndButtons.setBackground(Color.white);
		treeAndButtons.add(treePane);

		// TODO: enable following line for release
		/*
		 * listPanel.setMaximumSize(new
		 * Dimension(getContentPane().getSize().width,
		 * listPane.getSize().height+10));
		 */

		// move up and move down buttons
		upButton = new JButton(new ImageIcon("images\\arrow_top.gif"));
		upButton.addActionListener(this);
		upButton.setEnabled(true);
		downButton = new JButton(new ImageIcon("images\\arrow_down.gif"));
		downButton.addActionListener(this);
		downButton.setEnabled(true);
		openButton = new JButton("Open...");
		openButton.addActionListener(this);
		saveButton = new JButton("Save...");
		
		saveButton.addActionListener(this);
		JPanel opensavePanel = new JPanel();
		opensavePanel.setLayout(new BoxLayout(opensavePanel, BoxLayout.Y_AXIS));
		opensavePanel.setBackground(Color.white);
		opensavePanel.add(openButton);
		opensavePanel.add(saveButton);

		JPanel updownPanel = new JPanel();
		updownPanel.setLayout(new BoxLayout(updownPanel, BoxLayout.Y_AXIS));
		updownPanel.setBackground(Color.white);
		updownPanel.add(upButton);
		updownPanel.add(downButton);

		buttonPanel = new JPanel();
		buttonPanel.setLayout(new BorderLayout());
		buttonPanel.setBackground(Color.white);
		buttonPanel.setBorder(new EmptyBorder(1, 0, 1, 0));
		buttonPanel.add(updownPanel, BorderLayout.NORTH);
		buttonPanel.add(opensavePanel, BorderLayout.SOUTH);

		treeAndButtons.add(buttonPanel, BorderLayout.EAST);
		selectComponentPanel.add(treeAndButtons);

		JPanel selectdropdownPanel = new JPanel();
		selectdropdownPanel.setLayout(new BoxLayout(selectdropdownPanel,
				BoxLayout.Y_AXIS));
		selectdropdownPanel.setBackground(Color.white);
		selectdropdownPanel.setBorder(new EmptyBorder(5, 0, 5, 0));

		selectComponentBox = new JComboBox();
		selectComponentBox.setPreferredSize(new Dimension(250, 25));
		selectComponentBox.addActionListener(this);
		selectdropdownPanel.add(selectComponentBox);
		selectComponentPanel.add(selectdropdownPanel, BorderLayout.SOUTH);

		// A panel for the component settings
		settingsPanel = new JPanel();
		settingsPanel.setBackground(Color.white);
		Border settingsPanelBorder = BorderFactory.createTitledBorder("Edit Layout");
		JScrollPane settingsScroll = new JScrollPane(settingsPanel);
		settingsScroll.setBackground(Color.white);
		settingsScroll.setBorder(settingsPanelBorder);

		// status label
		statusLabel = new JLabel("Editor ready");
		JPanel labelPanel = new JPanel();
		labelPanel.setBackground(Color.white);
		labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.X_AXIS));
		labelPanel.add(statusLabel);

		mainVerticalPanel.add(selectComponentPanel, BorderLayout.NORTH);
		mainVerticalPanel.add(settingsScroll, BorderLayout.CENTER);
		mainVerticalPanel.add(labelPanel, BorderLayout.SOUTH);

		getContentPane().add(mainVerticalPanel);
		getContentPane().setVisible(true);
	}
	private void initializesettings() {
		// used vectors: suppProperties, suppBoxes
		suppBoxes = new Vector<JComboBox>();
		try {
			suppProperties = xmlHandler.getAnnotationfileHandler().getSupportedProperties();
			GridLayout lay = new GridLayout(suppProperties.size(), 2);

			lay.setVgap(5);

			settingsPanel.setLayout(lay);

			JComboBox box;
			JPanel panellb;
			JPanel paneldd;

			for (int i = 0; i < suppProperties.size(); i++) {
				panellb = new JPanel();
				paneldd = new JPanel();
				paneldd.setBackground(Color.white);
				panellb.setBackground(Color.white);
				panellb.add(new JLabel(suppProperties.get(i)));
				box = new JComboBox();
				box.setName(suppProperties.get(i));
				box.setPreferredSize(new Dimension(250, 25));
				suppBoxes.add(box);
				paneldd.add(box);
				settingsPanel.add(panellb);
				settingsPanel.add(paneldd);
			}
		}
		catch (EbFormsException e) {
			showError(e.getMessage());
			errorState();
			return;
		}
	}
	@SuppressWarnings("unchecked")
	private void correctOrder(DefaultMutableTreeNode node){
		Enumeration<DefaultMutableTreeNode> e = node.children();
		DefaultMutableTreeNode current;
		while (e.hasMoreElements()) {
			current = e.nextElement();
			ListItem item = (ListItem) current.getUserObject();
			String den = xmlHandler.getSchemafileHandler().getDictionaryEntryName(item);
			int pos = xmlHandler.getAnnotationfileHandler().readCCPosition(den);
			if (pos >= 0 && pos < node.getChildCount() -1) {
				int oudepos = node.getIndex(current);
				if (oudepos !=pos) {
					TreeNode andere = node.getChildAt(pos);
					node.insert(current, pos);			
					node.insert((DefaultMutableTreeNode)andere, oudepos);	
				}
			}
			correctOrder(current);
		}		
	}

	private void saveOrder(DefaultMutableTreeNode parent){
		DefaultMutableTreeNode current;
		ListItem item;
		String den ;
		for (int i = 0; i < parent.getChildCount(); i++) {
			current = (DefaultMutableTreeNode)parent.getChildAt(i);
			item= (ListItem) current.getUserObject();
			den = xmlHandler.getSchemafileHandler().getDictionaryEntryName(item);
			xmlHandler.getAnnotationfileHandler().writeCCPosition(den, i);
			saveOrder(current);		 
		}						
	}

	/**
	 * This method fills the dropdown boxes with the correct values
	 * @pre dictionaryItemName contains the name of correct dictionary entry name
	 */
	private void fillDropDownBoxes() {
		Vector<String> allowedValues;
		JComboBox currentbox;


		for (int i = 0; i < suppBoxes.size(); i++) {
			currentbox = suppBoxes.get(i);
			currentbox.removeActionListener(this);
			currentbox.removeAllItems();


			allowedValues = xmlHandler.getAnnotationfileHandler()
			.getAllowedPropertyValues(currentbox.getName(),dictionaryItemName);

			if (allowedValues.size() > 0){
				currentbox.addItem("[NONE]");
			} else {
				currentbox.addItem("[No property allowed]");
			}

			for (int j = 0; j < allowedValues.size(); j++) {
				currentbox.addItem(allowedValues.get(j));
			}
			currentbox.setSelectedItem(xmlHandler.getAnnotationfileHandler().readProperty(currentbox.getName(), dictionaryItemName));
			currentbox.addActionListener(this);
		}
	}
	private void handleDropDownEvent(ActionEvent evt) {
		JComboBox box = (JComboBox) evt.getSource();

		if (dictionaryItemName != null && box.getSelectedItem() != null){
			String sCurrentSelectedItem = (String)box.getSelectedItem(); 
			if (!sCurrentSelectedItem.equals(xmlHandler.getAnnotationfileHandler().readProperty(box.getName(), dictionaryItemName))) {
				if (!box.getSelectedItem().equals("[NONE]")){
					xmlHandler.getAnnotationfileHandler().writeProperty(box.getName(), (String) box.getSelectedItem(), dictionaryItemName);
				} else { //none
					xmlHandler.getAnnotationfileHandler().deleteProperty(box.getName(), dictionaryItemName);
				}
			}
		}
	}

	private void moveComponentUp() {
		TreePath path = tree.getSelectionPath();
		if (path != null && path.getPathCount() > 1) {
			DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
			int index = node.getParent().getIndex(node);
			if (index > 0) {
				((DefaultMutableTreeNode)node.getParent()).insert(node, index-1);
				tree.updateUI();
			}
		}
	}

	private void moveComponentDown() {
		TreePath path = tree.getSelectionPath();
		if (path != null && path.getPathCount() > 1) {
			DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
			int index = node.getParent().getIndex(node);
			if (index < node.getParent().getChildCount()-1) {
				((DefaultMutableTreeNode)node.getParent()).insert(node, index+1);
				tree.updateUI();
			}
		}
	}		


	/**
	 * Method used to show an error message to the user.
	 * @param s the message to display.
	 */
	private void showError(String s) {
		JOptionPane.showMessageDialog(null, "Error: " + s, "ERROR",
				JOptionPane.ERROR_MESSAGE);
	}
	private void errorState() {
		getContentPane().removeAll();
		getContentPane().add(new JLabel("A fatal error occured, restart this applet to try again"));
	}
	/**
	 * A simple method for showing messages. Not used right now
	 */
	private void showMessage(String s) {
		JOptionPane.showMessageDialog(getContentPane(), s);
	}
	/**
	 * This function opens an open file dialog. It attempts to only allow XML files.
	 * It sets the configFilename to the correct path.
	 * @return True if the operation succeeded, false otherwise.
	 */
	private boolean openFileDialog() {
		FileDialog fd = new FileDialog(new Frame(),"Select the configuration file", FileDialog.LOAD);
		fd.setFilenameFilter(new XMLFilenameFilter());
		// since the above line does not work in (all versions of) IE, the next one is added
		fd.setFile("*.xml");
		fd.setVisible(true);

		configFilename = fd.getDirectory() + fd.getFile();
		return new File(configFilename).exists();
	}
	/**
	 * This method opens a "save as" file-dialog and offers the user to enter a filename where to the
	 * file must be saved. Default is the same as the opened file.
	 */
	private void saveFileDialog() {
		try {
			//TODO: annotation file will be saved in the same directory as where the configuration
			//      file is stored so changes in directory structure is not supported.
			FileDialog fd = new FileDialog(new Frame(),"Save as...", FileDialog.SAVE);
			fd.setFilenameFilter(new XMLFilenameFilter());
			// since the above line does not work in (all versions of) IE, the next one is added
			fd.setFile(xmlHandler.getAnnotationfileName());
			fd.setVisible(true);

			String annotationFileName = fd.getFile();

			if (xmlHandler.getConfigFilePath().equals(fd.getDirectory())) {
				xmlHandler.getAnnotationfileHandler().saveAnnotationfile(fd.getDirectory() + annotationFileName);


				xmlHandler.saveConfigFile(annotationFileName);
			} else {
				showError("File must be saved in same directory as the config file");
			}
		} catch (EbFormsException e) {
			showError(e.getMessage());
			errorState();
		}

	}
	public void valueChanged(TreeSelectionEvent evt) {
		// Returns the last path element of the selection.
		// This method is useful only when the selection model allows a single
		// selection.
		DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();
		if (node == null)
			return; // Nothing selected.

		// else: handle element

		// 1: dictionary entry name --> status bar
		ListItem item = (ListItem) node.getUserObject();
		dictionaryItemName = xmlHandler.getSchemafileHandler().getDictionaryEntryName(item);
		statusLabel.setText(dictionaryItemName);

		// 2: fill dropdownboxes
		fillDropDownBoxes();

		// 3: fill selectcomponentbox
		selectComponentBox.removeActionListener(this);
		selectComponentBox.removeAllItems();

		if (dictionaryItemName != null) {
			Vector<String> hierarchie = XMLHandler.getAbstractHierarchy(dictionaryItemName);

			for (int i = 0; i < hierarchie.size(); i++) {
				selectComponentBox.addItem(hierarchie.get(i));
			}
			selectComponentBox.addActionListener(this);

		}
		// 4: definition --> tool tip
		String definition = xmlHandler.getSchemafileHandler().getDefinition(item);
		if (definition == null)
			definition = "item has no stored definition";
		tree.setToolTipText(item.toString() + ": " + definition);
	}
	/**
	 * This method handles events. It is called as soon as a button, or
	 * an element from the tree is selected. It then calls some other function
	 * to handle the results of the event.
	 */
	public void actionPerformed(ActionEvent evt) {
		if (evt.getSource() == saveButton) {
			saveOrder(root);
			statusLabel.setText("Saving file!");
			saveFileDialog();

			statusLabel.setText("File saved!");
		} else if (evt.getSource() == openButton) {
			this.init();
		} else if (evt.getSource() == upButton) {

			moveComponentUp();
		} else if (evt.getSource() == downButton) {

			moveComponentDown();
		} else if (evt.getSource() == selectComponentBox) {
			dictionaryItemName = (String) selectComponentBox.getSelectedItem();
			statusLabel.setText(dictionaryItemName);
			fillDropDownBoxes();
		}
		else {// drop-down value changed
			handleDropDownEvent(evt);
		}
	}
	private class XMLFilenameFilter implements FilenameFilter {
		public boolean accept(File dir, String name) {
			return name.endsWith(".xml");
		}
	}
}
